In [ ]:
epochs = 10

Parte 6 - Aprendizado Federado com MNIST usando uma CNN

Melhore o Aprendizado Federado com 10 linhas de código PyTorch + PySyft

Contexto

Federated Learning é uma técnica de Machine Learning muito fascinante e inovadora que visa a construção de sistemas que aprendem a partir de dados descentralizados. A idéia é que os dados permaneçam nas mãos de quem os gerou, isto é, seu possuidor (também conhecido como o Worker), o que ajuda a melhorar a privacidade e o direito de propriedade, assim o modelo é compartilhado entre os Workers. Uma aplicação imediata é, por exemplo, prever a próxima palavra em seu telefone celular quando você escreve texto: você não deseja que os dados usados no treinamento - ou seja, suas mensagens de texto - sejam enviados para um servidor central.

A ascensão do Federated Learning está, portanto, fortemente ligada à disseminação da conscientização sobre privacidade de dados, e o GDPR na UE, que faz cumprir a proteção de dados desde maio de 2018, atuou como um catalisador. Para antecipar a regulamentação, grandes companias como Apple ou Google começaram a investir maciçamente nessa tecnologia, especialmente para proteger a privacidade dos usuários de dispositivos móveis, mas não disponibilizaram suas ferramentas. Na OpenMined, acreditamos que qualquer pessoa disposta a conduzir um projeto de Machine Learning deve ser capaz de implementar ferramentas de proteção de privacidade com muito pouco esforço. Criamos ferramentas para criptografar dados em uma única linha conforme mencionado em nossa postagem no blog e agora lançamos nosso framework de Aprendizado Federado, que utiliza o nova versão do PyTorch 1.0 para fornecer uma interface intuitiva para criar modelos seguros e escaláveis.

Neste tutorial, usaremos diretamente o exemplo mais básico de treinamento de uma CNN no MNIST usando PyTorch e mostraremos como é simples implementar o Aprendizado Federado com ele usando nossa biblioteca PySyft. Analisaremos cada parte do exemplo e sublinharemos o código que foi alterado.

Você também pode encontrar esse material em nosso blog.

Autores:

Tradução:

Ok, vamos começar!

Imports e especificações do modelo

Primeiro fazemos os imports oficiais


In [ ]:
import torch
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torchvision import datasets, transforms

E então aqueles específicos para o PySyft. Em particular, definimos os workers remotos alice ebob.


In [ ]:
import syft as sy  # <-- NOVO: importe a biblioteca do PySyft
hook = sy.TorchHook(torch)  # <-- NOVO: gancho do PyTorch, ou seja, adicione funcionalidades extras para dar suporte ao Federated Learning
bob = sy.VirtualWorker(hook, id="bob")  # <-- NOVO: define os workers remotos bob
alice = sy.VirtualWorker(hook, id="alice")  # <-- NOVO: e alice

Definimos a configuração da tarefa de aprendizagem


In [ ]:
class Arguments():
    def __init__(self):
        self.batch_size = 64
        self.test_batch_size = 1000
        self.epochs = epochs
        self.lr = 0.01
        self.momentum = 0.5
        self.no_cuda = False
        self.seed = 1
        self.log_interval = 30
        self.save_model = False

args = Arguments()

use_cuda = not args.no_cuda and torch.cuda.is_available()

torch.manual_seed(args.seed)

device = torch.device("cuda" if use_cuda else "cpu")

kwargs = {'num_workers': 1, 'pin_memory': True} if use_cuda else {}

Carregamento e envio de dados para os workers

Primeiro carregamos os dados e transformamos o conjunto de dados de treinamento em um conjunto de dados federado dividido entre os workers usando o método .federate. Este conjunto de dados federado agora é fornecido para um Federated DataLoader. O conjunto de dados de teste permanece inalterado.


In [ ]:
federated_train_loader = sy.FederatedDataLoader( # <-- agora é um FederatedDataLoader 
    datasets.MNIST('../data', train=True, download=True,
                   transform=transforms.Compose([
                       transforms.ToTensor(),
                       transforms.Normalize((0.1307,), (0.3081,))
                   ]))
    .federate((bob, alice)), # <-- NOVO: distribuímos o dados entre todos os workers, agora é um FederatedDataset
    batch_size=args.batch_size, shuffle=True, **kwargs)

test_loader = torch.utils.data.DataLoader(
    datasets.MNIST('../data', train=False, transform=transforms.Compose([
                       transforms.ToTensor(),
                       transforms.Normalize((0.1307,), (0.3081,))
                   ])),
    batch_size=args.test_batch_size, shuffle=True, **kwargs)

Especificação da CNN

Aqui usamos exatamente a mesma CNN que do exemplo oficial.


In [ ]:
class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(1, 20, 5, 1)
        self.conv2 = nn.Conv2d(20, 50, 5, 1)
        self.fc1 = nn.Linear(4*4*50, 500)
        self.fc2 = nn.Linear(500, 10)

    def forward(self, x):
        x = F.relu(self.conv1(x))
        x = F.max_pool2d(x, 2, 2)
        x = F.relu(self.conv2(x))
        x = F.max_pool2d(x, 2, 2)
        x = x.view(-1, 4*4*50)
        x = F.relu(self.fc1(x))
        x = self.fc2(x)
        return F.log_softmax(x, dim=1)

Defina as funções de treino e teste

Para a função de treino, como os batches de dados são distribuídos entre alice e bob, você precisa enviar o modelo para o local certo para cada batch. Em seguida, você executa todas as operações remotamente com a mesma sintaxe que você está executando no PyTorch local. Quando terminar, você recupera o modelo atualizado e a perda (loss) para buscar melhorias.


In [ ]:
def train(args, model, device, federated_train_loader, optimizer, epoch):
    model.train()
    for batch_idx, (data, target) in enumerate(federated_train_loader): # <-- agora é um dataset distribuído
        model.send(data.location) # <-- NOVO: envie o modelo para o local certo
        data, target = data.to(device), target.to(device)
        optimizer.zero_grad()
        output = model(data)
        loss = F.nll_loss(output, target)
        loss.backward()
        optimizer.step()
        model.get() # <-- NOVO: receba o modelo de volta
        if batch_idx % args.log_interval == 0:
            loss = loss.get() # <-- NOVO: recupere a perda
            print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(
                epoch, batch_idx * args.batch_size, len(federated_train_loader) * args.batch_size,
                100. * batch_idx / len(federated_train_loader), loss.item()))

A função de teste não muda!


In [ ]:
def test(args, model, device, test_loader):
    model.eval()
    test_loss = 0
    correct = 0
    with torch.no_grad():
        for data, target in test_loader:
            data, target = data.to(device), target.to(device)
            output = model(data)
            test_loss += F.nll_loss(output, target, reduction='sum').item() # calcule a perda em cada batch
            pred = output.argmax(1, keepdim=True) # obtenha o índice de maior probabilidade logarítmica
            correct += pred.eq(target.view_as(pred)).sum().item()

    test_loss /= len(test_loader.dataset)

    print('\nTest set: Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\n'.format(
        test_loss, correct, len(test_loader.dataset),
        100. * correct / len(test_loader.dataset)))

Inicie o treinamento!


In [ ]:
%%time
model = Net().to(device)
optimizer = optim.SGD(model.parameters(), lr=args.lr) # TODO momentum não é suportado no momento

for epoch in range(1, args.epochs + 1):
    train(args, model, device, federated_train_loader, optimizer, epoch)
    test(args, model, device, test_loader)

if (args.save_model):
    torch.save(model.state_dict(), "mnist_cnn.pt")

Et voilà! Aqui você treinou um modelo a partir de dados remotos usando o Federated Learning!

Uma última coisa

Sei que há uma pergunta que você está ansioso para fazer: quanto tempo leva o treinamento com Federated Learning comparado ao treinamento com PyTorch normal?

O tempo de computação é na verdade menos do dobro do tempo usado para a execução normal do PyTorch! Mais precisamente, leva 1.9 vezes mais tempo, o que é muito pouco se comparado aos recursos que pudemos adicionar.

Conclusão

Como você observou, modificamos 10 linhas de código para atualizar o exemplo oficial do Pytorch no MNIST para uma configuração real de Aprendizado Federado!

Obviamente, existem dezenas de melhorias em que poderíamos pensar. Gostaríamos que o cálculo operasse em paralelo com os workers e realizasse a média federada (i.e. federated averaging), atualizando o modelo central a cada n batches apenas, para reduzir o número de mensagens que usamos para nos comunicarmos entre os workers, etc. Essas são características em que estamos trabalhando para tornar o Federated Learning pronto para um ambiente de produção e escreverêmos sobre assim que forem lançadas!

Agora você deve poder fazer Aprendizado Federado sozinho! Se você gostou disso e gostaria de se juntar ao movimento em direção à preservação da privacidade, propriedade descentralizada da IA e da cadeia de suprimentos da AI (dados), você pode fazê-lo das seguintes maneiras!

Dê-nos uma estrela em nosso repo do PySyft no GitHub

A maneira mais fácil de ajudar nossa comunidade é adicionando uma estrela nos nossos repositórios! Isso ajuda a aumentar a conscientização sobre essas ferramentas legais que estamos construindo.

Veja nossos tutoriais no GitHub!

Fizemos tutoriais muito bons para entender melhor como deve ser a Aprendizagem Federada e a proteção de Privacidade, e como estamos construindo as coisas básicas que precisamos para fazer com que isso aconteça.

Junte-se ao Slack!

A melhor maneira de manter-se atualizado sobre os últimos avanços é se juntar à nossa comunidade!

Contribua com o projeto!

A melhor maneira de contribuir para a nossa comunidade é se tornando um contribuidor do código! A qualquer momento, você pode acessar a página de Issues (problemas) do PySyft no GitHub e filtrar por "Projetos". Isso mostrará todas as etiquetas (tags) na parte superior, com uma visão geral de quais projetos você pode participar! Se você não deseja ingressar em um projeto, mas gostaria de codificar um pouco, também pode procurar mais mini-projetos "independentes" pesquisando problemas no GitHub marcados como "good first issue".

Doar

Se você não tem tempo para contribuir com nossa base de códigos, mas ainda deseja nos apoiar, também pode se tornar um Apoiador em nosso Open Collective. Todas as doações vão para hospedagem na web e outras despesas da comunidade, como hackathons e meetups!

Página do Open Collective do OpenMined


In [ ]: